﻿using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.Application;
using JetBrains.Application.Progress;
using JetBrains.DocumentModel;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Daemon;
using JetBrains.ReSharper.Daemon.Stages;
using JetBrains.ReSharper.Daemon.Stages.Dispatcher;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Intentions.Bulk;
using JetBrains.ReSharper.Intentions.Extensibility;
using JetBrains.ReSharper.Intentions.Extensibility.Menu;
using JetBrains.ReSharper.Intentions.QuickFixes.Bulk;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Parsing;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
using JetBrains.ReSharper.Psi.Files;
using JetBrains.ReSharper.Psi.Pointers;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;
using SPCAFContrib.ReSharper.Common;
using SPCAFContrib.ReSharper.Common.Attributes;
using SPCAFContrib.ReSharper.Common.Extensions;
using SPCAFContrib.ReSharper.Common.QuickFix;
using SPCAFContrib.ReSharper.Consts;
using SPCAFContrib.ReSharper.Inspection.Code.SPCAF;

[assembly: RegisterConfigurableSeverity(SPC050224Highlighting.CheckId,
  null,
  Consts.SPCAF_BEST_PRACTICES_GROUP,
  SPC050224Highlighting.CheckId + ": " + SPC050224Highlighting.Message,
  "Do not call SPList.Items[int] or SPList.Items[Guid]. Use SPList.GetItemByUniqueId(Guid) or SPList.GetItemById(int) instead.",
  Severity.WARNING,
  false, Internal = false)]

namespace SPCAFContrib.ReSharper.Inspection.Code.SPCAF
{
    [ElementProblemAnalyzer(new[] { typeof(IElementAccessExpression) }, HighlightingTypes = new[] { typeof(SPC050224Highlighting) })]
    [Applicability(
        IDEProjectType.SP2010FarmSolution |
        IDEProjectType.SPSandbox |
        IDEProjectType.SP2013FarmSolution |
        IDEProjectType.SPServerAPIReferenced)]
    public class DoNotUseListItemsByIndex : ElementProblemAnalyzer<IElementAccessExpression>
    {
        public enum ValidationResult : uint
        {
            Valid = 0,
            GetItemById = 1,
            GetItemByUniqueId = 2
        }
        protected override void Run(IElementAccessExpression element, ElementProblemAnalyzerData analyzerData,
            IHighlightingConsumer consumer)
        {
            IPsiSourceFile sourceFile = element.GetSourceFile();

            if (sourceFile != null)
            {
                if (sourceFile.HasExcluded(analyzerData.SettingsStore)) return;

                IProject project = sourceFile.GetProject();

                if (project != null)
                {
                    if (project.IsApplicableFor(this))
                    {
                        ValidationResult validationResult = IsInvalid(element);
                        if ((uint)validationResult > 0) 
                        {
                            consumer.AddHighlighting(
                                new SPC050224Highlighting(element, validationResult),
                                element.GetDocumentRange(),
                                element.GetContainingFile());
                        }
                    }
                }
            }
        }

        public static ValidationResult IsInvalid(IElementAccessExpression element)
        {
            ValidationResult result = ValidationResult.Valid;

            IExpressionType expressionType = element.Operand.GetExpressionType();

            if (expressionType.IsResolved)
            {
                var nextToken = element.Operand.GetNextMeaningfulToken();

                if (element.Operand.IsResolvedAsPropertyUsage(ClrTypeKeys.SPList, new[] {"Items"}) &&
                    (nextToken != null && nextToken.GetTokenType() == CSharpTokenType.LBRACKET))
                {
                    TreeNodeCollection<ICSharpArgument> arguments = element.Arguments;
                    ICSharpArgument firstArgument = arguments.First();
                    if (firstArgument.MatchingParameter != null)
                    {
                        result = ValidationResult.GetItemById;
                        if (firstArgument.MatchingParameter.Element.Type.IsGuid())
                            result = ValidationResult.GetItemByUniqueId;
                    }
                }
            }

            return result;
        }
    }

    [ConfigurableSeverityHighlighting(CheckId, CSharpLanguage.Name, OverlapResolve = OverlapResolveKind.NONE, ShowToolTipInStatusBar = true)]
    public class SPC050224Highlighting : IHighlighting
    {
        public const string CheckId = "SPC050224";
        public const string Message = "Do not call SPList.Items[]";

        public IElementAccessExpression Element { get; private set; }
        public DoNotUseListItemsByIndex.ValidationResult ValidationResult;

        public SPC050224Highlighting(IElementAccessExpression element, DoNotUseListItemsByIndex.ValidationResult validationResult)
        {
            this.ValidationResult = validationResult;
            this.Element = element;
        }

        #region IHighlighting Members

        public string ToolTip
        {
            get
            {
                return String.Format("{0}: {1}", CheckId, Message);
            }
        }
        public string ErrorStripeToolTip
        {
            get { return ToolTip; }
        }

        public int NavigationOffsetPatch
        {
            get { return 0; }
        }

        public bool IsValid()
        {
            return this.Element != null && this.Element.IsValid();
        }

        #endregion
    }

    [QuickFix]
    public class SPC050224Fix : QuickFixBase
    {
        private readonly SPC050224Highlighting _highlighting;

        public SPC050224Fix([NotNull] SPC050224Highlighting highlighting)
        {
            _highlighting = highlighting;
        }

        public override IEnumerable<IntentionAction> CreateBulbItems()
        {
            ISolution solution = _highlighting.Element.GetSolution();
            IPsiFiles psiFiles = solution.GetComponent<IPsiFiles>();
            IProjectFile projectFile = _highlighting.Element.GetSourceFile().ToProjectFile();

            Action<IDocument, IPsiSourceFile, IProgressIndicator> processFileAction =
                (document, psiSourceFile, indicator) =>
                {
                    if (!psiSourceFile.HasExcluded(psiSourceFile.GetSettingsStore()))
                    {
                        IEnumerable<ICSharpFile> csharpFiles =
                            psiFiles.GetPsiFiles<CSharpLanguage>(psiSourceFile).OfType<ICSharpFile>();
                        foreach (ICSharpFile csharpFile in csharpFiles)
                        {
                            new SPC050224FileHandler(document, csharpFile).Run(indicator);
                        }
                    }
                };

            var acceptProjectFilePredicate =
                BulkItentionsBuilderEx.CreateAcceptFilePredicateByPsiLanaguage<CSharpLanguage>(solution);
            var inFileFix = new BulkQuickFixInFileWithCommonPsiTransaction(projectFile, Text, processFileAction);
            var builder = new BulkQuickFixWithCommonTransactionBuilder(
                this, inFileFix, solution, Text, processFileAction, acceptProjectFilePredicate);

            return builder.CreateBulkActions(projectFile,
                IntentionsAnchors.QuickFixesAnchor, IntentionsAnchors.QuickFixesAnchorPosition);
        }

        public override bool IsAvailable(IUserDataHolder cache)
        {
            return _highlighting.IsValid();
        }

        protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
        {
            if (_highlighting.Element.IsValid())
            {
                SPC050224FileHandler.Fix(_highlighting.Element, solution);
            }

            return null;
        }

        public override string Text
        {
            get
            {
                switch (_highlighting.ValidationResult)
                {
                    case DoNotUseListItemsByIndex.ValidationResult.GetItemById:
                        return String.Format("Replace to SPList.{0}", "GetItemById");
                    case DoNotUseListItemsByIndex.ValidationResult.GetItemByUniqueId:
                        return String.Format("Replace to SPList.{0}", "GetItemByUniqueId");
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
    }

    public class SPC050224FileHandler
    {
        private readonly ICSharpFile _file;
        private readonly IDocument _document;

        public SPC050224FileHandler(IDocument document, ICSharpFile file)
        {
            this._file = file;
            this._document = document;
        }

        public void Run(IProgressIndicator pi)
        {
            List<IElementAccessExpression> statements = new List<IElementAccessExpression>();

            _file.ProcessThisAndDescendants(new RecursiveElementProcessor<IElementAccessExpression>(
                s =>
                {
                    DoNotUseListItemsByIndex.ValidationResult validationResult = DoNotUseListItemsByIndex.IsInvalid(s);
                    if ((uint)validationResult > 0) 
                        statements.Add(s);
                }));
            List<ITreeNodePointer<IElementAccessExpression>> nodes = statements.Select(x => x.CreateTreeElementPointer()).ToList();

            pi.Start(nodes.Count);
            foreach (ITreeNodePointer<IElementAccessExpression> treeNodePointer in nodes)
            {
                IElementAccessExpression statement = treeNodePointer.GetTreeNode();
                if (statement != null)
                {
                    Fix(statement, _file.GetSolution());
                }
                pi.Advance(1.0);
            }
        }

        public static void Fix(IElementAccessExpression element, ISolution solution)
        {
            CSharpElementFactory elementFactory = CSharpElementFactory.GetInstance(element);
            TreeNodeCollection<ICSharpArgument> arguments = element.Arguments;
            ICSharpArgument firstArgument = arguments.First();

            if (firstArgument.MatchingParameter != null)
            {
                string replacement = ".GetItemById";
                if (firstArgument.MatchingParameter.Element.Type.IsGuid())
                    replacement = ".GetItemByUniqueId";

                ICSharpExpression newElement =
                    elementFactory.CreateExpression(
                        element.GetText().Replace(".Items", replacement).Replace("[", "(").Replace("]", ")"));
                
                using (WriteLockCookie.Create(element.IsPhysical()))
                    element.ReplaceBy(newElement);
            }
        }
    }
}
